package com.harboursoftware.xstorage.web;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.commons.codec.binary.Base64;
import org.mortbay.log.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ListMultimap;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.harboursoftware.xstorage.XStorageException;
import com.harboursoftware.xstorage.util.DateUtils;

/**
 * The conditions in the policy document are used to validate the contents of the uploaded object. Each form field that
 * you specify in the form (except AWSAccessKeyId , signature, file, policy, and field names that have an x-ignore-
 * prefix) must be included in the list of conditions.
 * 
 * @author Simon Leung
 * @since 0.1
 */
public class PostPolicy {
    private final static Logger log = LoggerFactory.getLogger(PostPolicy.class);
    private Date expiration;
    @SuppressWarnings("unchecked")
    private List<PostPolicyCondition> conditions;
    @SuppressWarnings("unchecked")
    private Map<String, PostPolicyCondition> conditionMap;

    @SuppressWarnings("unchecked")
    public boolean matchs(ListMultimap<String, String> postFormFields) {
        Map<String, PostPolicyCondition> conditionMap = getConditionsAsMap();
        for (Entry<String, String> entry : postFormFields.entries()) {
            String key = entry.getKey();
            if (isIgnoredField(key)) {
                log.debug("[{}] is ignored key, continue", key);
                continue;
            }
            PostPolicyCondition condition = conditionMap.get(key);
            if (condition != null) {
                return condition.matchs(entry.getValue());
            }
        }
        return true;
    }

    private boolean isIgnoredField(String key) {
        return (key.equals("AWSAccessKeyId") || key.equals("signature") || key.equals("file") || key.equals("policy") || key
            .startsWith("x-ignore-"));
    }

    /**
     * 测试s3 policy错误时的响应
     * 
     * @param base64EncodedJson
     * @return
     * @throws XStorageException
     */
    public static PostPolicy fromBase64Json(String base64EncodedJson) throws XStorageException {
        String json = decodeBase64(base64EncodedJson);
        json = expandVariables(json);
        json = escapeCharacters(json);
        log.debug("post policy : {}", json);
        return fromJson(json);
    }
    
    private static String decodeBase64(String base64String) {
        try {
            byte[] base64Bytes = Base64.decodeBase64(base64String.getBytes("UTF-8"));
            return new String(base64Bytes, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            //ignore
        }
        return "";
    }

    /**
     * 变量赋值
     */
    private static String expandVariables(String policyJson) {
        //TODO:未完成
        return policyJson;
    }

    /**
     * 字符转义
     */
    private static String escapeCharacters(String policyJson) {
        //TODO:未完成
        return policyJson;
    }
    
    public static PostPolicy fromJson(String json) throws XStorageException {
        JsonParser parser = new JsonParser();
        JsonObject root = parser.parse(json).getAsJsonObject();
        
        PostPolicy policy = new PostPolicy();
        
        String expiration = root.getAsJsonPrimitive("expiration").getAsString();
        policy.setExpiration(DateUtils.parseDate(expiration, DateUtils.PATTERN_ISO8601));
        JsonArray conditions = root.getAsJsonArray("conditions");
        for(JsonElement codition : conditions) {
            if(codition.isJsonObject()) {
                Set<Entry<String, JsonElement>>  entrySet = codition.getAsJsonObject().entrySet();
                if(entrySet.size() != 1) {
                    //TODO 异常处理
                }
                for(Entry<String, JsonElement> entry : entrySet) {
                    String key = entry.getKey();
                    String values = entry.getValue().getAsString();
                    policy.addContition(PostPolicyCondition.createExactMatchesCondition(key, values));
                }
                
            } else if(codition.isJsonArray()) {
                JsonArray jsonArray = codition.getAsJsonArray();
                if(jsonArray.size() != 3) {
                  //TODO 异常处理
                }
                String match =  jsonArray.get(0).getAsString();
                if("starts-with".equals(match)) {
                    String key = jsonArray.get(1).getAsString().substring(1);
                    String prefix = jsonArray.get(2).getAsString();
                    policy.addContition(PostPolicyCondition.createStartsWithCondition(key, prefix));
                } else if("eq".equals(match)) {
                    String key = jsonArray.get(1).getAsString().substring(1);
                    String values = jsonArray.get(2).getAsString();
                    policy.addContition(PostPolicyCondition.createExactMatchesCondition(key, values));
                    
                } else if("content-length-range".equals(match)) {
                    long lower = jsonArray.get(1).getAsLong();
                    long upper = jsonArray.get(2).getAsLong();
                    policy.addContition(PostPolicyCondition.createSpecifyingRangesCondition(match, lower, upper));
                }
            }
        }
        return policy;
    }
    
    @SuppressWarnings("unchecked")
    public void addContition(PostPolicyCondition condition) {
        if(conditions == null) {
            conditions = new ArrayList<PostPolicyCondition>();
        }
        conditions.add(condition);
    }

    public Date getExpiration() {
        return expiration;
    }


    public void setExpiration(Date expiration) {
        this.expiration = expiration;
    }

    @SuppressWarnings("unchecked")
    public List<PostPolicyCondition> getConditions() {
        return conditions == null ? Collections.EMPTY_LIST : conditions;
    }

    @SuppressWarnings("unchecked")
    public void setConditions(List<PostPolicyCondition> conditions) {
        this.conditions = conditions;
    }

    @SuppressWarnings("unchecked")
    public Map<String, PostPolicyCondition> getConditionsAsMap() {
        if (conditions == null) {
            return Collections.EMPTY_MAP;
        }

        if (conditionMap != null) {
            return conditionMap;
        }

        conditionMap = new HashMap<String, PostPolicyCondition>();
        for (PostPolicyCondition condition : conditions) {
            conditionMap.put(condition.getKey(), condition);
        }
        return conditionMap;
    }
}
